﻿namespace MEF_Test.Bricks
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Linq;
    using System.Reflection;

    using Microsoft.Practices.Unity;

    /// <summary>
    /// A <see cref="DirectoryCatalog"/> which supports mapping a contract to a concrete type in a similar way to Unity's <see cref="UnityContainer.RegisterInstance"/>.
    /// </summary>    
    /// <remarks>This class extends the <see cref="DirectoryCatalog"/> so if there are multiple exports for a Part and a single Import (that is : an import which doesn't use <see cref="ImportManyAttribute"/>) it is possible to define some mapping information to define which part to use on the import.</remarks>
    public class MappableDirectoryCatalog : DirectoryCatalog
    {
        #region Constants and Fields

        /// <summary>
        /// Holds a list of the mappings between contracts and the type to use.
        /// </summary>
        private readonly Dictionary<string, string> _contractToTypeMappings;

        /// <summary>
        /// The MethodInfo of the <see cref="ComposablePartDefinition"/> GetDisplayName static extension method.
        /// </summary>
        /// <remarks>This field is set with a value retrieved using reflection. It is used to expose an internal method. As this is not supported by microsoft, it is subject to be broken when using an other version of the .Net framework.</remarks>
        private readonly MethodInfo _getDisplayNameMethod;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MappableDirectoryCatalog"/> class.
        /// </summary>
        /// <param name="directoryPath">The path to the directory.</param>
        /// <param name="contractToTypeMappings">The contract to type mappings.</param>
        public MappableDirectoryCatalog(string directoryPath, Dictionary<string, string> contractToTypeMappings) : base(directoryPath)
        {
            // HACK : we should find a cleaner way to do this : we want to know the name of the class which wants to be exported. So far this is the only workaround we've found since the ComposablePartDefinition has very few public members.
            _getDisplayNameMethod =
                Type.GetType(
                    "System.ComponentModel.Composition.Primitives.CompositionElementExtensions, System.ComponentModel.Composition, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
                    .GetMethod("GetDisplayName", new[] { typeof(ComposablePartDefinition) });
            
            // HACK : same reason : we need to do a bit of  reflection again
            // TODO : Use reflection to add a new parameter to the mappings to include the origin of the Import Definition in order to have a more fine-grained mapping. a mapping would then be able to map from the same contract to an other type depending of its origin ( i.e. the member the attribute is decorating ). The key would then be a tuple of Tuple<string, string> instead of the scalar string currentl used.            
            // string origin = (((System.ComponentModel.Composition.ReflectionModel.ReflectionImportDefinition)(definition)))._origin.DisplayName;
            this._contractToTypeMappings = contractToTypeMappings;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Gets the export definitions that match the constraint expressed by the specified definition.
        /// </summary>
        /// <param name="definition">The conditions of the <see cref="T:System.ComponentModel.Composition.Primitives.ExportDefinition"/> objects to be returned.</param>
        /// <returns>
        /// A collection of <see cref="T:System.Tuple`2"/> containing the <see cref="T:System.ComponentModel.Composition.Primitives.ExportDefinition"/> objects and their associated <see cref="T:System.ComponentModel.Composition.Primitives.ComposablePartDefinition"/> objects for objects that match the constraint specified by <paramref name="definition"/>.
        /// </returns>
        /// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.ComponentModel.Composition.Hosting.DirectoryCatalog"/> object has been disposed of.</exception>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="definition"/> is null.</exception>
        public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
        {            
            var candidates = base.GetExports(definition);              

            // if the definition is mapped, then resolve the correct mapping.            
            var contractName = definition.ContractName;
            
            if (_contractToTypeMappings.ContainsKey(contractName))
            {
                var mappedType = this._contractToTypeMappings[contractName];
                candidates = from candidate in candidates
                             where (string)this._getDisplayNameMethod.Invoke(null, new object[] { candidate.Item1 }) == mappedType
                             select candidate;
            }

            return candidates;
        }

        #endregion
    }
}